﻿using System;
using System.Globalization;
using System.IO;
using System.Web;
using System.Web.Mvc;
using Contrib.SecuredSocketsLayer.Models;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Mvc.Filters;
using Orchard.UI.Admin;

namespace Contrib.SecuredSocketsLayer.Filters {
    public class SecuredSocketsLayersFilter : FilterProvider, IActionFilter {
        private readonly IOrchardServices _services;

        public SecuredSocketsLayersFilter(IOrchardServices services) {
            _services = services;
        }

        public void OnActionExecuted(ActionExecutedContext filterContext) { }

        public void OnActionExecuting(ActionExecutingContext filterContext) {

            var request = filterContext.HttpContext.Request;

            var settings = _services.WorkContext.CurrentSite.As<SslSettingsPart>();

            if (settings == null) {
                return;
            }

            var isAdmin = AdminFilter.IsApplied(filterContext.RequestContext);

            var isAuthPage = (filterContext.ActionDescriptor.ActionName == "LogOn"
                              || filterContext.ActionDescriptor.ActionName == "ChangePassword"
                              || filterContext.ActionDescriptor.ActionName == "AccessDenied"
                              || filterContext.ActionDescriptor.ActionName == "Register"
                              || filterContext.ActionDescriptor.ActionName.StartsWith("ChallengeEmail", StringComparison.OrdinalIgnoreCase))
                             && filterContext.ActionDescriptor.ControllerDescriptor.ControllerName == "Account";

            var isProtected = IsRequestProtected(request, settings);

            var secure =
                settings.AdminEnabled && isAdmin
                || settings.AuthenticationEnabled && isAuthPage
                || settings.CustomEnabled && isProtected;

            // redirect to a secured connection ?
            if(secure && !request.IsSecureConnection) {
                var builder = new UriBuilder(request.Url) {
                    Scheme = Uri.UriSchemeHttps,
                    Port = 443
                };

                var secureHostName = settings.SecureHostName;
                SetHost(secureHostName, builder);

                filterContext.Result = new RedirectResult(builder.Uri.ToString());
                return;
            }

            // non auth page on a secure canal
            // nb: needed as the ReturnUrl for LogOn doesn't force the scheme to http, and reuses the current one
            if (!secure && request.IsSecureConnection) {
                var builder = new UriBuilder(request.Url) {
                    Scheme = Uri.UriSchemeHttp,
                    Port = 80
                };

                var insecureHostName = settings.InsecureHostName;
                SetHost(insecureHostName, builder);

                filterContext.Result = new RedirectResult(builder.Uri.ToString());
                return;
            }
        }

        private static void SetHost(string hostName, UriBuilder builder) {
            if (!string.IsNullOrWhiteSpace(hostName)) {
                var splitSecuredHostName = hostName.Split(new[] {':'}, StringSplitOptions.RemoveEmptyEntries);
                if (splitSecuredHostName.Length == 2) {
                    int port;
                    if (int.TryParse(splitSecuredHostName[1], NumberStyles.Integer, CultureInfo.InvariantCulture,
                                     out port)) {
                        builder.Port = port;
                        hostName = splitSecuredHostName[0];
                    }
                }
                builder.Host = hostName;
            }
        }

        private static bool IsRequestProtected(HttpRequestBase request, SslSettingsPart settings) {
            var match = false;
            var sr = new StringReader(settings.Urls ?? "");
            string url;

            while (!match && null != (url = sr.ReadLine())) {
                url = url.Trim();
                match = IsMatched(url, request);
            }

            return match;
        }

        private static bool IsMatched(string url, HttpRequestBase request) {
            if (url.StartsWith("~/")) {
                url = url.Substring(2);
                var appPath = request.ApplicationPath;
                if (appPath == "/")
                    appPath = "";
                url = string.Format("{0}/{1}", appPath, url);
            }

            if (!url.Contains("?"))
                url = url.TrimEnd('/');

            var requestPath = request.Path;
            if (!requestPath.Contains("?"))
                requestPath = requestPath.TrimEnd('/');

            return url.EndsWith("*")
                ? requestPath.StartsWith(url.TrimEnd('*'), StringComparison.OrdinalIgnoreCase)
                : string.Equals(requestPath, url, StringComparison.OrdinalIgnoreCase);            
        }
    }
}